高级设置菜单完结版
// ==UserScript==
// @name Advanced Settings Dialog
// @description Adds an "Advanced Settings" menu item to configure various Firefox settings via a dialog.
// @author (Original author), Gemini
// @version 2.0
// ==/UserScript==
(function () {
  'use strict';
  // --- Configuration ---
  const SETTINGS = [
    ['userChrome.enableRightClickCloseTab', '右键关闭标签页 (CTRL+右键弹出菜单)'],
    ['userChrome.enableRightClickUndoTab', '右键标签栏撤销关闭 (CTRL+右键弹出菜单)'],
    ['browser.tabs.closeTabByDblclick', '双击关闭标签页'],
    ['browser.tabs.closeWindowWithLastTab', '关闭最后一个标签页时关闭窗口'],
    ['browser.urlbar.openintab', '地址栏在新标签页打开'],
    ['browser.tabs.loadBookmarksInTabs', '书签栏在新标签页打开'],
    ['browser.search.openintab', '搜索框在新标签页打开'],
    ['browser.urlbar.trimURLs', '地址栏隐藏 http/https'],
    ['browser.urlbar.scotchBonnet.enableOverride', '启用下拉选择搜索引擎'],
    ['browser.tabs.insertAfterCurrent', '在当前标签页右侧打开新标签页'],
    ['browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar', '主页搜索框输入时跳转到地址栏'],
    ['browser.bookmarks.openInTabClosesMenu', '禁用书签菜单 CTRL+多次点击打开多个书签'],
    ['browser.chrome.toolbar_tips', '鼠标悬停小提示'],
    ['browser.tabs.hoverPreview.enabled', '启用标签页缩略图'],
    ['browser.compactmode.show', '定制密度增加紧凑模式'],
    ['userChrome.hideWindowControls', '隐藏窗口控制按钮'],
    ['userChrome.useSerifFont', '界面使用衬线字体']
  ];
  // --- Global State & Cached Elements ---
  const cleanupFunctions = new Set();
  let styleEl = null;
  let dialogEl = null;
  // --- Preference Handling ---
  const getPref = (k, d = false) => Services.prefs.getBoolPref(k, d);
  const setPref = (k, v) => Services.prefs.setBoolPref(k, v);
  const resetPrefs = () => SETTINGS.forEach(([k]) => Services.prefs.clearUserPref(k));
  // --- Centralized Update Handlers ---
  const PREF_HANDLERS = {
    'userChrome.enableRightClickCloseTab': setupRightClickHandler,
    'userChrome.enableRightClickUndoTab': setupRightClickHandler,
    'userChrome.hideWindowControls': applyStyle,
    'userChrome.useSerifFont': applyStyle
  };
  // --- Styling ---
  function applyStyle() {
    if (!styleEl) {
      styleEl = document.createElement('style');
      styleEl.id = 'adv-settings-style';
      document.head.appendChild(styleEl);
      cleanupFunctions.add(() => styleEl?.remove());
    }
    styleEl.textContent = `
      #adv-settings-dialog {
        padding: 15px; border: 1px solid #0004; border-radius: 4px;
        background: -moz-Dialog; color: var(--main-color, #000);
        min-width: 400px; max-width: 600px; z-index: 10000;
        font: message-box; font-size: 14px; line-height: 14px;
        box-shadow: 0 1px 4px #0003;
      }
      #adv-settings-dialog::backdrop { background: #0002; }
      
      /* New style for the header container */
      .dialog-header {
          display: flex;
          justify-content: space-between;
          align-items: center;
          margin-bottom: 15px; /* Maintain gap between header and form */
      }
      #adv-settings-dialog h2 {
        font-size: 16px;
        margin: 0; /* Remove default margin as container handles spacing */
      }
      .adv-settings-form { display: flex; flex-direction: column; gap: 12px; }
      .setting-item { display: flex; align-items: center; gap: 8px; }
      .setting-label { flex: 1; cursor: pointer; }
      .button-row { display: flex; justify-content: flex-end; gap: 8px; margin-top: 20px; }
      
      /* Dynamic Styles */
      ${getPref('userChrome.hideWindowControls') ? '.titlebar-buttonbox-container { display: none !important; }' : ''}
      ${getPref('userChrome.useSerifFont') ? '* { font-family: "serif" !important; }' : ''}
    `;
  }
  // --- Dynamic Event Handling (Right Click) ---
  function handleTabContainerRightClick(e) {
    if (e.button !== 2 || e.ctrlKey) return; // Only for right-click without CTRL
    const tab = e.target.closest('.tabbrowser-tab');
    if (getPref('userChrome.enableRightClickCloseTab') && tab) {
      e.preventDefault(); e.stopPropagation();
      gBrowser[tab.multiselected ? 'removeMultiSelectedTabs' : 'removeTab'](tab, { animate: true, triggeringEvent: e });
    } else if (getPref('userChrome.enableRightClickUndoTab') && !tab) {
      if (SessionStore.getClosedTabCount(window) > 0) {
        e.preventDefault(); e.stopPropagation();
        SessionStore.undoCloseTab(window, 0);
      }
    }
  }
  function setupRightClickHandler() {
      const container = gBrowser?.tabContainer;
      if (!container) return;
      
      // Remove existing listener to prevent duplicates
      container.removeEventListener('contextmenu', handleTabContainerRightClick, true);
      // Add listener only if at least one feature is enabled
      if (getPref('userChrome.enableRightClickCloseTab') || getPref('userChrome.enableRightClickUndoTab')) {
          container.addEventListener('contextmenu', handleTabContainerRightClick, true);
      }
  }
  // --- UI Creation and Management ---
  function createAndShowDialog() {
    if (!dialogEl) {
      // Create dialog only once
      dialogEl = document.createElement('dialog');
      dialogEl.id = 'adv-settings-dialog';
      cleanupFunctions.add(() => dialogEl?.remove());
      const dialogClickHandler = e => {
        const rect = dialogEl.getBoundingClientRect();
        const isInDialog = (
          rect.top <= e.clientY && e.clientY <= rect.bottom &&
          rect.left <= e.clientX && e.clientX <= rect.right
        );
        if (!isInDialog) {
          dialogEl.close();
        }
      };
      dialogEl.addEventListener('click', dialogClickHandler);
      cleanupFunctions.add(() => dialogEl.removeEventListener('click', dialogClickHandler));
      // New header container for title and reset button
      const headerContainer = document.createElement('div');
      headerContainer.className = 'dialog-header';
      dialogEl.appendChild(headerContainer);
      const titleEl = document.createElement('h2');
      titleEl.textContent = '高级设置';
      headerContainer.appendChild(titleEl);
      const resetBtn = document.createElement('button');
      resetBtn.textContent = '重置全部';
      headerContainer.appendChild(resetBtn); // Append to headerContainer
      const resetHandler = () => {
        resetPrefs();
        // Refresh UI after prefs are cleared
        requestAnimationFrame(() => refreshDialogUI());
      };
      resetBtn.addEventListener('click', resetHandler);
      cleanupFunctions.add(() => resetBtn.removeEventListener('click', resetHandler));
      // --- Helper to create a single setting item ---
      const createCheckboxItem = (pref, labelText) => {
        const div = document.createElement('div');
        div.className = 'setting-item';
        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.id = `pref-${pref}`;
        checkbox.checked = getPref(pref);
        const changeHandler = () => {
          setPref(pref, checkbox.checked);
          PREF_HANDLERS[pref]?.(); // Trigger specific handler if exists
        };
        checkbox.addEventListener('change', changeHandler);
        cleanupFunctions.add(() => checkbox.removeEventListener('change', changeHandler));
        const label = document.createElement('label');
        label.htmlFor = checkbox.id;
        label.className = 'setting-label';
        label.textContent = labelText;
        div.append(checkbox, label);
        return div;
      };
      const form = document.createElement('form');
      form.className = 'adv-settings-form';
      SETTINGS.forEach(([pref, label]) => form.appendChild(createCheckboxItem(pref, label)));
      dialogEl.appendChild(form);
      
      document.body.appendChild(dialogEl);
    }
    // --- Refresh UI state before showing ---
    const refreshDialogUI = () => {
        SETTINGS.forEach(([pref]) => {
            const cb = dialogEl.querySelector(`#pref-${CSS.escape(pref)}`);
            if (cb) cb.checked = getPref(pref);
        });
        // Also refresh styles and listeners that depend on these settings
        applyStyle();
        setupRightClickHandler();
    };
    refreshDialogUI();
    dialogEl.showModal();
  }
  // --- Menu Item Integration ---
  function addMenuItem() {
    const appMenu = document.getElementById('appMenu-popup');
    const settingsBtn = document.getElementById('appMenu-settings-button');
    if (!appMenu || !settingsBtn || document.getElementById('appMenu-advanced-settings-button')) {
      return false;
    }
    const btn = document.createXULElement('toolbarbutton');
    btn.id = 'appMenu-advanced-settings-button';
    btn.className = 'subviewbutton';
    btn.setAttribute('label', '高级设置');
    const commandHandler = e => {
      e.stopPropagation();
      appMenu.hidePopup();
      createAndShowDialog();
    };
    btn.addEventListener('command', commandHandler);
    cleanupFunctions.add(() => btn.removeEventListener('command', commandHandler));
    settingsBtn.insertAdjacentElement('afterend', btn);
    return true;
  }
  
  function addMenuItemOnFirstOpen() {
    const appMenuPopup = document.getElementById('appMenu-popup');
    if (!appMenuPopup) return;
    const onMenuShowing = () => {
      if (addMenuItem()) {
        appMenuPopup.removeEventListener('popupshowing', onMenuShowing);
      }
    };
    appMenuPopup.addEventListener('popupshowing', onMenuShowing);
    cleanupFunctions.add(() => appMenuPopup.removeEventListener('popupshowing', onMenuShowing));
  }
  // --- Initialization and Cleanup ---
  function cleanup() {
    // Unregister preference observers
    for (const [pref, handler] of Object.entries(PREF_HANDLERS)) {
      Services.prefs.removeObserver(pref, handler);
    }
    // Remove the global right-click listener
    gBrowser?.tabContainer.removeEventListener('contextmenu', handleTabContainerRightClick, true);
    // Run all registered cleanup functions
    cleanupFunctions.forEach(fn => {
      try { fn(); } catch (e) { console.error('Cleanup function failed:', e); }
    });
    
    cleanupFunctions.clear();
  }
  function init() {
    // Initial setup
    for (const [pref, handler] of Object.entries(PREF_HANDLERS)) {
      Services.prefs.addObserver(pref, handler);
      handler(); // Run once on startup
    }
    // Wait for the DOM to be ready before inserting the menu item
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', addMenuItemOnFirstOpen, { once: true });
    } else {
      addMenuItemOnFirstOpen();
    }
    
    window.addEventListener('unload', cleanup, { once: true });
  }
  init();
})();
2025-07-13
浏览635
登录后评论
评论
1